#!/usr/bin/perl

################## -COPYRIGHTS & WARRANTY- #########################
## It is provided without any warranty of fitness
## for any purpose. You can redistribute this file
## and/or modify it under the terms of the GNU
## Lesser General Public License (LGPL) as published
## by the Free Software Foundation, either version 3
## of the License or (at your option) any later version.
## (see http://www.opensource.org/licenses for more info)
####################################################################

########################### -TODO- #################################
## 1. Add option --destination also.

## -Author: Anver Hisham <anverhisham@gmail.com>
####################################################################

use List::Util qw(first);

use strict;
use warnings;
use POSIX;
$SIG{CHLD} = 'IGNORE';      # To avoid zombie child processes
use File::Spec;             # For using 'chdir File::Spec->updir;'
use List::Util qw(first);   # For getting first index of the search item in an Array. (Warning: the word 'first is reserved from now on!!)
#sub cutOptions,checkForRegularExpressions;
## -Get the script file & folder names, & change directory to script folder. - ##
use Cwd 'abs_path';
use File::Basename;
use List::Util qw(sum);
use constant false => 0;
use constant true  => 1;

our $currentFileVersionNumber = '1.0';
our $trashFolder=$ENV{"HOME"}."/.Trash_backup";
our $timePattern = qr/\d{4}:\d{2}:\d{2}:\d{2}:\d{2}:\d{2}/;
our $registerPattern = qr/-register:.+--/;
our $prefixPattern = qr/$timePattern($registerPattern)?/;
our $currentTime = getCurrentTime();

exit main();

sub main {
    ## -To get fileNames starting as -- -foo
    my @filesToRetrieve = ();	##cutOptions(\@ARGV,'all',' ','--');
    ## -Input Options Grepping
    my($recent,$date,$register,$reg) = cutOptions(\@ARGV,' ','--recent','--date','--register','--reg');
    my($version,$isRegex,$list,$l,$keep) = cutOptions(\@ARGV,'bool','--version','--regex','--list','-l','--keep');
    if($reg ne '') { $register = $reg; }
    $list = $list | $l;
    if(!$list && $date eq '' && $register eq '' && !$isRegex && !scalar(@ARGV) && $recent eq '') { $recent = 0; }		## -If no option specified, then pick most recent.
    if($version) { print "$currentFileVersionNumber\n"; exit 0; }
    ## -Filter out all other(@bashOptions) options
    my @bashOptions = ();
    map { m/^-/ ? push(@bashOptions,$_) : push(@filesToRetrieve,$_) } @ARGV;
    ## -If no file is specified, then grep for all files
    if(!scalar(@filesToRetrieve)) { @filesToRetrieve = ('.*'); $isRegex = true; }
    
    ## -Get all files in Trash folder & Filter it
    my @filesInTrash = split(' ',`ls $trashFolder |sort -r;`); map{s/^.*\///, s/\s*$//} @filesInTrash;
    @filesInTrash = reverse(sort(@filesInTrash));     			## To make sure that latest file overwrites old file
    
    ############ ******************** START FILTERING OF FILES ************************* ##############
    ## -Filter w.r.t '--register' option
    if($register ne '') { @filesInTrash = grep(/^$timePattern-register:$register--/,@filesInTrash); }
    ## -Filter w.r.t '--date' option
    my($startTimeForFiles,$endTimeForFiles) = ('1900:01:01:01:01:01',$currentTime);
    if($date ne '') {
	if($date =~ m/[a-z]/i) {
	    $startTimeForFiles = getTimeStringFromClientFormat($date);
	}
	elsif($date=~m/\.\./) {
	    $date =~ s/(?<!\d)(\d)(?!\d)/0$1/g;     ## -Prepend '0's for single digits
	    if($date=~m/\d\.\./) {
		$startTimeForFiles=$date;
		$startTimeForFiles=~s/\.\..*//;
		$startTimeForFiles = getTimeStringFromClientFormat($startTimeForFiles,'floor');
	    }
	    if($date=~m/\.\.\d/) {
		$endTimeForFiles=$date;
		$endTimeForFiles=~s/.*\.\.//;
		$endTimeForFiles = getTimeStringFromClientFormat($endTimeForFiles,'ceil');
	    }
	}
	else {
	    $startTimeForFiles = getTimeStringFromClientFormat($date,'floor');
	    $endTimeForFiles = getTimeStringFromClientFormat($date,'ceil');
	}
    }
    @filesInTrash = filterFileWrtTime($startTimeForFiles,'after',@filesInTrash);
    @filesInTrash = filterFileWrtTime($endTimeForFiles,'before',@filesInTrash);
    ## -Filter w.r.t fileNames specified option
    if(@filesToRetrieve) {
        my @filteredFiles;
        foreach my $fileToRetrieve(@filesToRetrieve) {
            if($isRegex) {
                @filteredFiles = (@filteredFiles,grep(/^$prefixPattern$fileToRetrieve/,@filesInTrash));
            }
            else {
                @filteredFiles = (@filteredFiles,grep(/^$prefixPattern\E$fileToRetrieve\Q/,@filesInTrash));
            }
        }
        @filesInTrash = @filteredFiles;
    }
    
    ## -Start creating the recent index for @filesInTrash
    my @timeStampPerRecent = @filesInTrash;
    map{s/($timePattern).*/$1/} @timeStampPerRecent;
    @timeStampPerRecent = getUniqueElements(@timeStampPerRecent);
    my @recentIndexPerFileInTrash = getRecentIndexPerFileInTrash(\@filesInTrash,\@timeStampPerRecent);
    ## -Filter w.r.t '--recent' option
    if($recent ne '') {
	my $startIndex=1; my $endIndex = scalar(@timeStampPerRecent);
        if($recent =~ m/^\d+$/) {
	    $startIndex=$recent; $endIndex = $recent;
        }
        elsif($recent =~ m/\.\./) {
            if($recent =~ m/^\d+/) { $startIndex=$&; }
            if($recent =~ m/\d+$/) { $endIndex=$&; }
        }
        else {
            print "Error(ret): Specify recent in proper format (See man ret for more info)!! \n"; exit 1;
        }
	## -Filter w.r.t time-stamp
	my @filterOutput=();
	foreach my $iRecent($startIndex..$endIndex) {
	    @filterOutput = (@filterOutput,grep(/^$timeStampPerRecent[$iRecent]/,@filesInTrash));
	}
	@filesInTrash = @filterOutput;
	@recentIndexPerFileInTrash = getRecentIndexPerFileInTrash(\@filesInTrash,\@timeStampPerRecent);
    }
    ############ ______________________  ALL FILTERING IS OVER _______________________________ ##############
    
    ############ ******************** RETRIEVE/PRINT FILTERED FILES ************************* ##############
    my($originalFile,$indx)=('',1);
    ## -Remove Duplicates
    if(!$list) {
	my @originalFiles = @filesInTrash;
	map{s/$prefixPattern//} @originalFiles;
	@filesInTrash = @filesInTrash[getUniqueElementIndices(@originalFiles)];
    }
    ## -Get Max length of registerNames, for using in final printing...
    my @registerNamesPerFile = @filesInTrash;
    map{s/$timePattern(($registerPattern)?).*/$1/} @registerNamesPerFile;
    my $registerFieldWidth = 0;
    foreach(@registerNamesPerFile) { $registerFieldWidth = max($registerFieldWidth,length($_));}
    $registerFieldWidth = max(0,$registerFieldWidth-length("-register:"));
    ## -Iterate for each file
    foreach my $iFile(0..scalar(@filesInTrash)-1) {
        #$originalFile = (-d $filesInTrash[$iFile])? '.' : $filesInTrash[$iFile];
        $originalFile = $filesInTrash[$iFile];
        $originalFile =~ s/($timePattern)($registerPattern)?//;
        if($list) {
	    if(-d "$trashFolder/$filesInTrash[$iFile]") {	## -To Append '/' to folderNames
		$originalFile = $originalFile."/";
	    }
	    my $prefixTimeOfFile = $1;
	    my $register = $2;
	    if(defined($register)) { $register =~ s/^-register://; $register =~ s/--$//;}
	    else { $register = ''; }
	    $register = sprintf("%${registerFieldWidth}s",$register);
            $prefixTimeOfFile =~ s/:/\//;  $prefixTimeOfFile =~ s/:/\//;  $prefixTimeOfFile =~ s/:/ /;
	    print "$recentIndexPerFileInTrash[$iFile] $prefixTimeOfFile $register $originalFile \n";
        }
        else {
	    my $bashCommandToRun = join(' ',@bashOptions);
	    if(-d "$trashFolder/$filesInTrash[$iFile]") {
		print `mv $trashFolder/$filesInTrash[$iFile] $trashFolder/$originalFile; rm -rf $originalFile;`;
		$bashCommandToRun = $bashCommandToRun." $trashFolder/$originalFile .;";
	    }
	    else {
		$bashCommandToRun = $bashCommandToRun." $trashFolder/$filesInTrash[$iFile] $originalFile;";
	    }
	    if($keep) {
		$bashCommandToRun ="cp -rf $bashCommandToRun";
	    }
	    else {
		$bashCommandToRun ="mv -f $bashCommandToRun";
	    }
	    print `$bashCommandToRun`;
        }
        $indx++;
    }
    ############ ______________________ RETRIEVE/PRINT FILTERED FILES OVER __________________ ##############
    
    return 0;
}


#################################################################################################################################
###########################################---- LOCAL FUNCTIONS ----------------################################################
#################################################################################################################################



## \Warning: @timeStampPerRecent is expected to have no duplicate elements
sub getRecentIndexPerFileInTrash {
    my @filesInTrash = @{$_[0]};
    my @timeStampPerRecent = @{$_[1]};
    my @recentIndexPerFileInTrash = @filesInTrash;
    for my $iFile(0..scalar(@filesInTrash)-1) {
	($recentIndexPerFileInTrash[$iFile]) = grep {$filesInTrash[$iFile] =~ m/^$timeStampPerRecent[$_]/} 0..$#timeStampPerRecent;
    }
    return @recentIndexPerFileInTrash;
}

sub getTimeStringFromClientFormat {
    if(scalar(@_)!=1 && scalar(@_)!=2) {print "Error(getTimeStringFromClientFormat) : Number of inputs must be 1 or 2"; exit 1; }
    my $timeInClientFormat = $_[0];
    my $option = ''; if(scalar(@_)>1) { $option = $_[1]; }
    my @output = split(':',$currentTime);
    if($timeInClientFormat =~ m/[a-z]/i) {
	my $outputString;
	SWITCH: {
	$timeInClientFormat=~m/^\d+s(econds?)?$/i && do {$timeInClientFormat=~s/s//; $outputString = addToTime($currentTime,-$timeInClientFormat,'seconds'); last SWITCH; };
	$timeInClientFormat=~m/^\d+m(inutes?)?$/i && do {$timeInClientFormat=~s/m//; $outputString = addToTime($currentTime,-$timeInClientFormat,'minutes'); last SWITCH; };
	$timeInClientFormat=~m/^\d+d(ays?)?$/ && do {$timeInClientFormat=~s/d//; $outputString = addToTime($currentTime,-$timeInClientFormat,'days'); last SWITCH; };
	$timeInClientFormat=~m/^\d+months?$/ && do {$timeInClientFormat=~s/months?//; $outputString = addToTime($currentTime,-$timeInClientFormat,'months'); last SWITCH; };
	$timeInClientFormat=~m/^\d+y(ears?)?$/ && do {$timeInClientFormat=~s/years?//; $outputString = addToTime($currentTime,-$timeInClientFormat,'years'); last SWITCH; };
	}
	@output = split(':',$outputString);
    }
    else {
	if($option=~m/floor/i) { @output[3,4,5] = (0,0,0); }
	elsif($option=~m/ceil/i) { @output[3,4,5] = (23,59,59); }
	if($timeInClientFormat =~ m/^\s*(\d+\/)?(\d+\/)?(\d+\+?)??(\d+:)?(\d+:)?(\d+)\s*$/) {
	    if(defined($2)) {			## Two '/'s
		if(defined($5)) {			## 2001/9/11+12:10:02(September 11th)
		    @output[0,1,2,3,4,5] = ($1,$2,$3,$4,$5,$6);
		}
		elsif(defined($4)) {		##  2001/9/11+12:10 (noon 12.10pm)
		    @output[0,1,2,3,4] = ($1,$2,$3,$4,$6);
		}
		elsif(defined($3)) {		## 2001/9/11+12(noon 12pm),
		    @output[0,1,2,3] = ($1,$2,$3,$6);
		}
		else {				## 2001/9/11(September 11th)
		    @output[0,1,2] = ($1,$2,$3);
		}
	    }
	    elsif(defined($1)) {			## One '/'s
		if(defined($5)) {			## 9/11+12:10:02(September 11th)
		    @output[1,2,3,4,5] = ($1,$3,$4,$5,$6);
		}
		elsif(defined($4)) {		##  9/11:12:10 (noon 12.10pm)
		    @output[1,2,3,4] = ($1,$3,$4,$6);
		}
		elsif(defined($3)) {		## 9/11:12(noon 12pm),
		    @output[1,2,3] = ($1,$3,$6);
		}
		else {				## 9/11(September 11th of current year)
		    @output[1,2] = ($1,$6);
		}
	    }
	    elsif(defined($3)) {		 ## No '/'s, but '+' is present
		if(defined($5)) {			## 11+12:10:02(11th of current month),
		    @output[2,3,4,5] = ($3,$4,$5,$6);
		}
		elsif(defined($4)) {		##  11+12:10 (noon 12.10pm of 11th current month)
		    @output[2,3,4] = ($3,$4,$6);
		}
		elsif(defined($3)) {		## 11+12 (noon 12pm of 11th current month),
		    @output[2,3] = ($3,$6);
		}
		else {				## 11+(12 'o' clock)
		    print "Error(getTimeStringFromClientFormat): Inputted date doesn't match to standard format YYYY/MM/DD+hh:mm:ss \n"; exit 1;
		}
	    }
	    else {		 			## No '/'s & no '+'
		if(defined($5)) {		##  12:10:02 (noon 12.10 past 2 seconds)
		    @output[3,4,5] = ($4,$5,$6);
		}
		elsif(defined($4)) {		## 12:10(noon 12.10pm),
		    @output[3,4] = ($4,$6);
		}
		else {				## 12(12 'o' clock)
		    $output[3] = ($6);
		}
	    }
	}
	else {
	    print "Error(getTimeStringFromClientFormat): Inputted date doesn't match to standard format YYYY/MM/DD+hh:mm:ss \n"; exit 1;
	}
    }
    map{s/[^0-9]//g} @output;
    return sprintf("%d:%02d:%02d:%02d:%02d:%02d",@output);
}


sub filterFileWrtTime {
    my($cutOffTime,$when,@inputtedFileNames) = @_;
    if($when ne 'before' && $when ne 'after') {print 'Error(filterFileWrtTime): 2nd input Argument must be before/after ...'; exit 1;}
    my @timePerFiles = @inputtedFileNames;
    map{s/.*($timePattern).*/$1/} @timePerFiles;
    map{s/://g} @timePerFiles;
    $cutOffTime =~ s/://g;
    my @output = ();
    foreach my $index(0..scalar(@timePerFiles)-1) {
        if($when eq 'before' &&  $timePerFiles[$index] le $cutOffTime) {
            @output = (@output,$inputtedFileNames[$index]);
        }
        elsif($when eq 'after' &&  $timePerFiles[$index] ge $cutOffTime) {
            @output = (@output,$inputtedFileNames[$index]);
        }
    }
    return @output;
}


########## ########## ########## -Time Manipulation Functions- ########## ############ ########## 
########## -Time Manipulation Functions- ############
## \Warning: localtime(4) return month-1. (ie month start from '0' instead of '1' !!)
sub getCurrentTime {
    my @currentTimeLocal = localtime();
    return convertLocalTimeArrayToTimeString(@currentTimeLocal);
}

## -Output is 2012:10:20:12:52:20, for 2012 Oct 20th noon 12:52
sub addToTime {
    my $time = shift(@_);
    my $toAdd = shift(@_);
    my $addFormat;
    if(!@_) { $addFormat = 'days'; }
    else { $addFormat = shift(@_); }
    if(@_) {print "Error(addToTime): Number of inputs expected is 2 or 3 !!!"; exit 1; }
    my @timeArray = convertTimeStringToLocalTimeArray($time);
    SWITCH: {
        $addFormat eq 'seconds' && do { $timeArray[0] += $toAdd; last SWITCH; };
        $addFormat eq 'minutes' && do { $timeArray[1] += $toAdd; last SWITCH; };
        $addFormat eq 'hours' && do { $timeArray[2] += $toAdd; last SWITCH; };
        $addFormat eq 'days' && do { $timeArray[3] += $toAdd; last SWITCH; };
        $addFormat eq 'months' && do { $timeArray[4] += $toAdd; last SWITCH; };
        $addFormat eq 'years' && do { $timeArray[5] += $toAdd; last SWITCH; };
    }
    @timeArray = localtime mktime(@timeArray);
    return convertLocalTimeArrayToTimeString(@timeArray);
}

## -Expected input format is year:month:day:hour:minute:second
sub convertTimeStringToLocalTimeArray {
    my $timeString = shift(@_);
    my @timeArray = split(':',$timeString);
    @timeArray = reverse(@timeArray);
    $timeArray[4] -= 1;			## Make month start from '0'
    $timeArray[5] -= 1900;		## Make Year start from 1900.
    return @timeArray;
}

## -Expected input is an array with format same as localtime output
sub convertLocalTimeArrayToTimeString {
    my @timeArray = @_;
    $timeArray[4] += 1;			## Make month start from '1'
    $timeArray[5] += 1900;		## Make Proper Year (not starting from 1900).
    my $timeString = sprintf("%d:%02d:%02d:%02d:%02d:%02d",@timeArray[5,4,3,2,1,0]);
    return $timeString;
}
#####################################################################

########################################################################################################

#################################################################################################################################
###########################################--- GENERIC FUNCTIONS ----------------################################################
#################################################################################################################################
sub cutOptions {
    my @outputs = ();
    my ($refToinArgs,$delimiter,@optionStrings) = @_;
    my $configuration ='';
    if($delimiter=~m/^all$/i) { $configuration = 'all'; $delimiter = shift(@optionStrings);}
    if($delimiter eq '') { $delimiter=' '; }				## -Make space as default delimiter

    if (ref($refToinArgs)) {
        for(my $iOption=0; $iOption<scalar(@optionStrings); $iOption++) {
            my $output;
            my $optionString = $optionStrings[$iOption];
            for(my $i=0; $i<scalar(@{$refToinArgs}); $i++) {
                if($refToinArgs->[$i] !~ m/^$optionString$/i) {
                    next;
                }
                else {
                    $output = splice(@{$refToinArgs},$i,1);
                    if($delimiter eq 'bool') {
                        $output = true;
                    }
                    elsif($output =~ m/\s$optionString\s*$delimiter\s*\w+/i ) {             	## -level=1
                        $output =~ s/\s$optionString\s*$delimiter//i;
                    }
                    elsif($output =~ m/\s$optionString\s*$delimiter/i) {                 	## -level= 1
                        $output = splice(@{$refToinArgs},$i,1);
                    }
                    elsif($delimiter eq ' ') {                 				## -level 1
                        $output = splice(@{$refToinArgs},$i,1);
                    }
                    elsif($refToinArgs->[$i] =~ m/$delimiter/) {                     	## -level = 1
                        $output = splice(@{$refToinArgs},$i,1);
                        $output = splice(@{$refToinArgs},$i,1);
                    }
                    else {
                        print "Error(cutOptions): Place proper delimter($delimiter) for options!!!";
                        exit 0;
                    }
                    $output =~ s/\s*//;                
		    if($configuration eq 'all') { push(@outputs,$output); }
                    last;
                }
            }
            if(!defined($output)) {
                if($delimiter eq 'bool') {
                    $output=false;
                }
                else {
                    $output='';
                }
            }
	    if($configuration ne 'all') { push(@outputs,$output); }
        }
    }
    else { 														# Not a reference
        print "Error(cutOptions): First input argument is not a reference, Can't be trimmed...";
        exit 0;
    }
    trimSpacesFromBothEnd(\@outputs);
    return @outputs;
}


######### Advantage: Trim an array of array of array..... ##########
######### Input: One/Multiple references to Scalar/Array... ##########
######### Picked from multipleRun.pl  ###############
sub trimSpacesFromBothEnd {
	my $input = $_[0];
	
	if (scalar(@_)>1) { 												# Input is an array
	    foreach my $input(@_) {
		trimSpacesFromBothEnd($input);
	    }
	}
	
	if ( UNIVERSAL::isa($input,'REF') ) {							    			# Reference to a Reference
		trimSpacesFromBothEnd(${$input});
	}
	elsif ( ! ref($input) ) { 												# Not a reference
	    print "Error(trimSpacesFromBothEnd): Not a reference, Can't be trimmed...";
	    exit 0;
	}
	elsif ( UNIVERSAL::isa($input,'SCALAR')) {  										# Reference to a scalar
		chomp(${$input});
		${$input} =~ s/^\s+//g;
		${$input} =~ s/\s+$//g;		
	}
	elsif ( UNIVERSAL::isa($input,'ARRAY') ) { 										# Reference to an array
		foreach my $element(@{$input}) {
			trimSpacesFromBothEnd(\$element);
		}
	}
	elsif ( UNIVERSAL::isa($input,'HASH') ) { 										# Reference to a hash
	    print "Error(trimSpacesFromBothEnd): Reference to an hash, Can't be trimmed...";
	    exit 0;
	}
	elsif ( UNIVERSAL::isa($input,'CODE') ) { 										# Reference to a subroutine
	    print "Error(trimSpacesFromBothEnd): Reference to an subroutine, Can't be trimmed...";
	    exit 0;
	}
}

## -Remove duplicate elements
sub getUniqueElements {
    my %seen;
    my @uniqElements = grep { ! $seen{$_}++ } @_;
    return @uniqElements;
}

## -Get indices of unique elements (lower index would get preference)
sub getUniqueElementIndices {
    my @inputs = @_;
    my %seen;
    my @uniqElements = grep { ! $seen{$inputs[$_]}++ } 0..$#inputs;
    return @uniqElements;
}

###### Return: Maximum value of Input Array... #####
sub max {
        my @sortedInput = sort {$a <=> $b} @_;
        return $sortedInput[-1];
}

##### Return: Minimum value of Input Array... #####
sub min {
        my @sortedInput = sort {$a <=> $b} @_;
        return $sortedInput[0];
}

